package aupdg.data.model.impl;

import aupdg.data.model.Edge;
import aupdg.data.model.Ldg;
import aupdg.data.model.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Concrete implementation of an LDG.
 *
 * @author AA and CJ
 */
public class AuLdg implements Ldg {

    /**
     * Counter of node IDs.
     */
    private int nodeIdCounter = 0;
    /**
     * List of node IDs.
     */
    private List<Integer> nodeIds;
    /**
     * Adjacent Edges of a node.
     */
    private List<List<Edge>> adjacentEdges;
    /**
     * Position of the node for adjacentEdges.
     */
    private Map<Integer, Integer> posNodeAdjEdges;
    /**
     * Maps nodeId with the (unique) node having this ID.
     */
    private Map<Integer, Node> idNode;
    /**
     * If LDG has already a entry-node. Just one entry node per LDG.
     */
    private boolean hasEntryNode;
    /**
     * Entry Node in LDG. At most 1 per LDG.
     */
    AuEntryNode entryNode;
    /**
     * List of if-nodes IDs.
     */
    private List<Integer> ifNodesIds;
    /**
     * List of transition-nodes' IDs. A transition correspond to an event in the
     * log.
     */
    private List<Integer> transitionNodesIds;
    private List<Edge> cDepEdges;
    private List<Edge> dDepEdges;
    private List<Node> predicateNodes;

    /**
     * Constructor LDG.
     */
    public AuLdg() {
        nodeIds = new ArrayList<>();
        adjacentEdges = new ArrayList<>();
        posNodeAdjEdges = new HashMap<>();
        idNode = new HashMap<>();
        ifNodesIds = new ArrayList<>();
        transitionNodesIds = new ArrayList<>();
        hasEntryNode = false;
        cDepEdges = new ArrayList<>();
        dDepEdges = new ArrayList<>();
        predicateNodes = new ArrayList<>();
    }

    /**
     * Return next available ID for creating a node.
     *
     * @return
     */
    private int getNextId() {
        return this.nodeIdCounter;
    }

    @Override
    public void addNode(Node node) {
        int id = getNextId();
        if (node instanceof AuEntryNode) {
            if (this.hasEntryNode) {
                System.out.println("Err entry node already inserted");
                return;
            } else {
                this.entryNode = (AuEntryNode) node;
                this.entryNode.setNodeIdInGraph(id);
            }
        } else if (node instanceof AuTransitionNode) {
            this.transitionNodesIds.add(id);
        } else if (node instanceof AuIfNode) {
            this.ifNodesIds.add(id);
        } else if (node instanceof AuPredicateNode) {
            predicateNodes.add(node);
        }
        node.setNodeIdInGraph(id);
        nodeIds.add(id);
        adjacentEdges.add(new ArrayList<Edge>());
        posNodeAdjEdges.put(id, adjacentEdges.size() - 1);
        idNode.put(id, node);
        this.nodeIdCounter++;
    }

    @Override
    public void addEdge(int sourceNodeId, Edge edge) {
        //TODO: agregar validacion por si el nodo no se ha agregado al ldg
        int pos = posNodeAdjEdges.get(sourceNodeId);
        adjacentEdges.get(pos).add(edge);
        if (edge instanceof AuControlDependenceEdge) {
            this.cDepEdges.add(edge);
        }
        if (edge instanceof AuDataDependenceEdge) {
            this.dDepEdges.add(edge);
        }
    }

    @Override
    public List<Edge> getAdjacentEdgesFromNode(Node node) {
        int pos = posNodeAdjEdges.get(node.getId());
        return adjacentEdges.get(pos);
    }    
    
    @Override
    public List<Node> getPredicateNodes() {
        return this.predicateNodes;
    }

    @Override
    public Node getNode(int nodeId) {
        return idNode.get(nodeId);
    }

    @Override
    public List<Integer> getNodeIds() {
        return nodeIds;
    }

    @Override
    public List<List<Edge>> getAdjacentEdges() {
        return adjacentEdges;
    }

    @Override
    public List<Node> getTransitionNodes() {
        List<Node> transitionNodes = new ArrayList<>();
        for (int i = 0; i < this.transitionNodesIds.size(); i++) {
            transitionNodes.add(getNode(this.transitionNodesIds.get(i)));
        }
        return transitionNodes;
    }

    @Override
    public List<Node> getIfNodes() {
        List<Node> ifNodes = new ArrayList<>();
        for (int i = 0; i < this.ifNodesIds.size(); i++) {
            ifNodes.add(getNode(this.ifNodesIds.get(i)));
        }
        return ifNodes;
    }

    @Override
    public AuEntryNode getEntryNode() {
        return this.entryNode;
    }

    @Override
    public List<Edge> getControlDepEdges() {
        return this.cDepEdges;
    }

    @Override
    public List<Edge> getDataDepEdges() {
        return this.dDepEdges;
    }

    @Override
    public boolean asString() {
        for (int i = 0; i < adjacentEdges.size(); i++) {
            System.out.println();
            System.out.print(idNode.get(i).getLabel() + idNode.get(i).getId() + "|");
            List<Edge> edges;
            edges = adjacentEdges.get(i);
            for (int j = 0; j < edges.size(); j++) {
                Edge edge = edges.get(j);
                if (edge instanceof AuControlDependenceEdge) {
                    System.out.print(edge.getTarget().getLabel() + edge.getTarget().getId() + "(C) - ");
                } else {
                    System.out.print(edge.getTarget().getLabel() + "(D) - ");
                }

            }
        }
        System.out.println();
        return true;
    }

}
